use anyhow::anyhow;
use anyhow::ensure;
use anyhow::Ok;
use anyhow::Result;
use chrono::Utc;
use surrealdb::opt::PatchOp;
use surrealdb::sql::Thing;

use crate::db;
use crate::db::FUDB;
use crate::db::FUHLC;
use crate::model;
use crate::model::indicators::INDICATOR_GROUP_KEYS;
use crate::model::kline::Klt;
use crate::model::malines::ma::MAConfig;
use crate::model::malines::MA_LINE_GROUP_KEYS;
use crate::model::trade::trade_strategy;
use crate::model::trade::trade_strategy::TradeStrategy;
use crate::model::trade::trade_strategy::TradeStrategyIndicator;
use crate::model::trade::trade_strategy::TradeStrategyMaLine;

use crate::model::FudaRecord;

const THING_TB: &str = "trade_strategy";

///
/// update or create a trade_strategy record
///
/// @param trade_strategy: trade_strategy's basic information
///
pub async fn upsert(trade_strategy: &trade_strategy::TradeStrategy) -> Result<Option<FudaRecord>> {
    println!("(trade_strategy::upsert) trade_strategy: {:?}", trade_strategy);

    let id = trade_strategy::TradeStrategy::id_from(&trade_strategy.klt, trade_strategy.seqno)?;
    let thing = Thing::from((THING_TB, id.as_str()));

    let old_trade_strategy: Option<trade_strategy::TradeStrategy> = FUDB.select((
        THING_TB,
        id.as_str(),
    )).await?;
    println!(
        "(trade_strategy::upsert) old_trade_strategy: {:?} thing={:?}",
        old_trade_strategy,
        thing
    );

    let mut updated: Option<FudaRecord> = None;
    let mut to_update = false;
    let timestamp = FUHLC.new_timestamp().to_string();
    // Update a record with a specific ID
    let mut patch = FUDB.update((THING_TB, id.as_str())).patch(
        PatchOp::replace("/last_updated", timestamp)
    );

    match old_trade_strategy {
        Some(TradeStrategy { id: Some(t), .. }) => {
            updated = Some(FudaRecord { id: t.into() });
        }
        _ => {
            patch = patch.patch(PatchOp::replace("/klt", &trade_strategy.klt));
            patch = patch.patch(PatchOp::replace("/seqno", &trade_strategy.seqno));
            to_update = true;
        }
    }

    if let Some(v) = &trade_strategy.name {
        patch = patch.patch(PatchOp::replace("/name", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.desc {
        patch = patch.patch(PatchOp::replace("/desc", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.script_verified {
        patch = patch.patch(PatchOp::replace("/script_verified", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.script {
        patch = patch.patch(PatchOp::replace("/script", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.kl_window {
        patch = patch.patch(PatchOp::replace("/kl_window", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.indicators {
        patch = patch.patch(PatchOp::replace("/indicators", v));
        to_update = true;
    }

    if let Some(v) = &trade_strategy.malines {
        patch = patch.patch(PatchOp::replace("/malines", v));
        to_update = true;
    }

    println!("(trade_strategy::upsert) before updated: {:?}", updated);

    if to_update {
        updated = patch.await.map_err(|err| anyhow!(err))?;
    }

    println!("(trade_strategy::upsert) after updated: {:?}", updated);

    Ok(updated)
}

///
/// delete trade_strategy by it's id , e.g. SH.STK.600519
///
pub async fn delete(trade_strategy_id: &str) -> Result<trade_strategy::TradeStrategy> {
    let rs: Option<trade_strategy::TradeStrategy> = FUDB.delete((
        THING_TB,
        trade_strategy_id,
    )).await?;
    match rs {
        Some(trade_strategy) => Ok(trade_strategy),
        None =>
            Err(
                anyhow!(
                    format!("Failed to delete, TradeStrategy not found for {}", trade_strategy_id)
                )
            ),
    }
}

/// select trade_strategies
///
/// klt: [Klt](super::kline::Klt)
///
/// pi: u16, page index, start from 0
///
/// pn: u16, page size,
///
pub async fn select(
    klt: Option<Klt>,
    pi: u16,
    pn: u16
) -> Result<Vec<trade_strategy::TradeStrategy>> {
    let mut q_str = r#"SELECT * FROM trade_strategy ORDER BY klt,seqno LIMIT $limit START $start"#;
    if let Some(_) = &klt {
        q_str =
            r#"SELECT * FROM trade_strategy WHERE klt=$klt ORDER BY klt,seqno LIMIT $limit START $start"#;
    }

    let start = pi * pn;

    // Fetch kline data within the specified date range using bind variables
    let mut query = FUDB.query(q_str);

    if let Some(t_) = &klt {
        query = query.bind(("klt", t_)); // Bind the start_date variable
    }

    query = query.bind(("limit", pn)).bind(("start", start));

    // println!("query={:?}", query);

    let mut response = query.await?;

    // println!("\nresponse= {:?}\n", response);

    let values: Vec<trade_strategy::TradeStrategy> = response.take(0)?;

    Ok(values)
}

/// find strategy
///
/// strategy_id: &str
///
///
pub async fn find_trade_strategy(strategy_id: &str) -> Result<trade_strategy::TradeStrategy> {
    let id = Thing::from((THING_TB, strategy_id));

    let strategy: Option<trade_strategy::TradeStrategy> = FUDB.query(r#"SELECT * FROM $id"#)
        .bind(("id", id)).await?
        .take(0)?;

    match strategy {
        Some(mut v) => {
            // . get all indicators
            let mut ts_indicators = Vec::<TradeStrategyIndicator>::new();
            for igk in INDICATOR_GROUP_KEYS.iter() {
                let mut is_there = false;
                if let Some(tsis) = &v.indicators {
                    for ig in tsis.iter() {
                        if igk == &ig.gk {
                            is_there = true;
                        }
                    }
                }
                if !is_there {
                    let tsi = TradeStrategyIndicator {
                        gk: igk.clone(),
                        is_used: false,
                    };
                    ts_indicators.push(tsi);
                }
            }
            if ts_indicators.len() > 0 {
                match v.indicators {
                    Some(vv) => {
                        ts_indicators.extend(vv);
                        v.indicators = Some(ts_indicators);
                    }
                    None => {
                        v.indicators = Some(ts_indicators);
                    }
                }
            }

            // get all malines
            let mut ts_malines = Vec::<TradeStrategyMaLine>::new();
            for mlk in MA_LINE_GROUP_KEYS.iter() {
                for period in MAConfig::default().periods.iter() {
                    let mut is_there = false;
                    if let Some(tsmls) = &v.malines {
                        for ml in tsmls.iter() {
                            if mlk == &ml.gk && ml.period == *period {
                                is_there = true;
                            }
                        }
                    }
                    if !is_there {
                        let tsml = TradeStrategyMaLine {
                            gk: mlk.clone(),
                            period: period.clone(),
                            is_used: false,
                        };
                        ts_malines.push(tsml);
                    }
                }
            }
            if ts_malines.len() > 0 {
                match v.malines {
                    Some(vv) => {
                        ts_malines.extend(vv);
                        v.malines = Some(ts_malines);
                    }
                    None => {
                        v.malines = Some(ts_malines);
                    }
                }
            }

            Ok(v)
        }
        None => Err(anyhow!("Not found strategy for id={strategy_id}")),
    }
}

pub async fn update_trade_strategy(
    id: &str,
    trade_strategy: &trade_strategy::TradeStrategy
) -> Result<trade_strategy::TradeStrategy> {
    let mut found = find_trade_strategy(id).await?;

    if let Some(v) = &trade_strategy.name {
        found.name = Some(v.clone());
    }

    if let Some(v) = &trade_strategy.desc {
        found.desc = Some(v.clone());
    }

    if let Some(v) = &trade_strategy.script_verified {
        found.script_verified = Some(v.clone());
    }

    if let Some(v) = &trade_strategy.script {
        found.script = Some(v.clone());
    }

    if let Some(v) = &trade_strategy.kl_window {
        found.kl_window = Some(v.clone());
    }

    let _upsert = upsert(&found).await?;

    Ok(found)
}

pub async fn update_trade_strategy_indicator(
    id: &str,
    ts_indicator: &TradeStrategyIndicator
) -> Result<trade_strategy::TradeStrategy> {
    let mut found = find_trade_strategy(id).await?;
    println!("(trade_strategy::update_trade_strategy_indicator) found={:?}\n\n", found);
    let mut is_there = false;
    if let Some(tsis) = &mut found.indicators {
        for tsi in tsis.iter_mut() {
            if tsi.gk == ts_indicator.gk {
                *tsi = ts_indicator.clone();
                is_there = true;
                break;
            }
        }
    }
    if !is_there {
        match found.indicators {
            Some(vv) => {
                let mut ts_indicators = vv;
                ts_indicators.push(ts_indicator.clone());
                found.indicators = Some(ts_indicators);
            }
            None => {
                found.indicators = Some(vec![ts_indicator.clone()]);
            }
        }
    }

    let _upsert = upsert(&found).await?;

    Ok(found)
}

pub async fn update_trade_strategy_maline(
    id: &str,
    ts_maline: &TradeStrategyMaLine
) -> Result<trade_strategy::TradeStrategy> {
    let mut found = find_trade_strategy(id).await?;
    println!("(trade_strategy::update_trade_strategy_maline) found={:?}\n\n", found);
    let mut is_there = false;
    if let Some(tsmls) = &mut found.malines {
        for tsml in tsmls.iter_mut() {
            if tsml.gk == ts_maline.gk && tsml.period == ts_maline.period {
                *tsml = ts_maline.clone();
                is_there = true;
                break;
            }
        }
    }
    if !is_there {
        match found.malines {
            Some(vv) => {
                let mut ts_malines = vv;
                ts_malines.push(ts_maline.clone());
                found.malines = Some(ts_malines);
            }
            None => {
                found.malines = Some(vec![ts_maline.clone()]);
            }
        }
    }

    let _upsert = upsert(&found).await?;

    Ok(found)
}

pub async fn publish_trade_strategy(strategy_id: &str) -> Result<Option<FudaRecord>> {
    println!("(trade_strategy::publish_trade_strategy) strategy_id={}", strategy_id);

    let mut found = find_trade_strategy(strategy_id).await?;
    println!("(trade_strategy::publish_trade_strategy) found={:?}\n\n", found);

    ensure!(matches!(found.script_verified, Some(true)), "backtest is not valid yet!");

    found.is_online = Some(true);

    let upserted = upsert(&found).await?;

    let securities = db::security::select(&None, None, None, None, 0, 10000).await?;
    for (idx, security) in securities.iter().enumerate() {
        match &security.id {
            Some(security_id) => {
                let bot = model::trade::trade_bot_with::TradeBot {
                    id: None,
                    security_id: Some(security_id.clone()),
                    strategy_id: Some(Thing::from(("trade_strategy", strategy_id))),
                    is_running: Some(false),
                    last_updated: Some(Utc::now().to_string()),
                    ..Default::default()
                };
                println!("(trade_strategy::publish_trade_strategy) TradeBot[{idx}]={:?}", bot);
                let rs = db::trade::trade_bot_with::upsert(&bot).await?;
                println!("(trade_strategy::publish_trade_strategy)   rs={:?}", rs);
            }
            None => todo!(),
        }
    }
    Ok(upserted)
}

#[cfg(test)]
mod test {
    use crate::db;
    use crate::db::FUHLC;
    use crate::model::kline::Klt;
    use crate::model::trade::trade_strategy;

    #[tokio::test]
    async fn test_upsert_trade_strategy() {
        let strategy_script =
            r#"
print("==========================");print("<indicators/macd.rhai>");

fn should_buy(kline) {
    if kline.macd.signal0.buy > 10 {
        return true;
    }
    return false;
}

fn should_sell(kline ) {
    if kline.macd.signal0.sell > 10  {
        return true;
    }
    return false;
}

let  buy_signal = false;
let  sell_signal = false;
for kline in Tapi.klines() { // Iterate in reverse to start from the most recent

    if should_buy(kline) {
        buy_signal = true;
        break;
    } else if should_sell(kline) {
        sell_signal = true;
        break;
    }

}

if buy_signal {
    print("--- (macd.rhai) Executing buy order for buy_signal= " + buy_signal);
    Tapi.buy(100);
} else if sell_signal {
    print("--- (macd.rhai) Executing sell order for sell_signal= " + sell_signal);
    Tapi.sell(100);
}

print("</indicators/macd.rhai>");
        "#;
        let _ = db::init().await.unwrap();
        for i in 0..103 {
            let trade_strategy = trade_strategy::TradeStrategy {
                klt: Klt::DAY,
                seqno: i,
                name: Some(format!("strategy{i}")),
                desc: Some(format!("strategy desc{i}")),
                script_verified: Some(false),
                script: Some(strategy_script.to_string()),
                ..Default::default()
            };

            // Update a trade_strategy record with a specific id
            let res = db::trade::trade_strategy::upsert(&trade_strategy).await;

            println!("res {:?}", res);
        }
    }

    #[tokio::test]
    async fn test_find_trade_strategy() {
        let _ = db::init().await.unwrap();
        let trade_strategy = trade_strategy::TradeStrategy {
            klt: Klt::DAY,
            seqno: 1,
            name: Some(format!("strategy1")),
            desc: Some(format!("strategy desc1")),
            script_verified: Some(true),
            script: Some(r#" strategy script 1"#.to_string()),
            ..Default::default()
        };
        let upserted = db::trade::trade_strategy::upsert(&trade_strategy).await.unwrap();

        // Update a trade_strategy record with a specific id
        let found = db::trade::trade_strategy
            ::find_trade_strategy(
                &trade_strategy::TradeStrategy::id_from(&Klt::DAY, 1).unwrap()
            ).await
            .unwrap();

        println!("found {:?}", found);

        assert_eq!(upserted.unwrap().id, found.id.unwrap());
    }

    /// Need to call first: test_update_kline_from_eastmoney to update klines for "513130"
    #[tokio::test]
    async fn test_select_strategies() {
        // generate a timestamp
        let ts = FUHLC.new_timestamp();
        println!("\nts = {}", ts.to_string());

        let _ = db::init().await.unwrap();

        let result = db::trade::trade_strategy::select(None, 0, 10).await;

        println!("result= {:?}\n", result);
        assert_eq!(result.is_ok(), true);

        if let Ok(values) = &result {
            for (index, value) in values.iter().enumerate() {
                println!(
                    "[{:02}]= {:?} klt:{} seqno:{} name:{:?}",
                    index,
                    value,
                    value.klt,
                    value.seqno,
                    value.name
                );
            }
        }
    }
}
